package com.example.demo.mqserver.core;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: 张
 * Date: 2024-08-15
 * Time: 20:18
 */

import com.example.demo.common.MqException;

/**
 * 使用这个类来实现交换机的转发规则
 * 同时也借助这个类,验证bindingKey是否合法
 */
public class Router {
    /**
     * 1.数字,字母,下划线
     * 2. 使用"."把整个routingKey分成多个部分
     * 3.支持使用 * 和 # 作为通配符
     */
    public boolean checkBindingKey(String bindingKey) {
        if (bindingKey.length() == 0) {
            //直接交换机的时候,bindingKey就是空的(人家是直接把routingKey当作队列名字来玩的)
            //扇出交换机也是一样的
            return true;
        }
        //检查
        for (int i = 0; i < bindingKey.length(); i++) {
            char ch = bindingKey.charAt(i);
            //判断大写
            if(ch >= 'A' && ch <= 'Z') {
                continue;
            }
            //判定小写
            if (ch >= 'a' && ch <= 'z') {
                continue;
            }
            //判定阿拉伯数字
            if (ch >= '0' && ch <= '9') {
                continue;
            }
            //判定下划线 点 * #
            if (ch == '_' || ch == '.' || ch == '*' || ch == '#') {
                continue;
            }
            return false;
        }

        //检查* 和 # 是否是独立的部分
        //aaa.*.bbb(合法) ; aaa.#b.ccc(非法)
        //在正则表达式中,"."是属于一个特殊的符号,此时只是想当做原始的文本进行切割,此时就要进行转义 -- \.
        //如果当前是在Java的环境下进行的编写,此时要想使用\.(Java中\也是一个特殊字符).此时就需要对\进行转义 ---> "\\."
        //第一个\是给第二个\进行转义的(在Java中),转义成文本的第二个\在为.在正则表达是中进行转义
        String[] words = bindingKey.split("\\.");
        for (String word:words) {
            //检查word的长度,如果长度大于1 而且 还包含了 * 或者 # 则非法
            if(word.length() > 1 && (word.contains("*") || word.contains("#"))) {
                //非法
                return false;
            }
        }

        // 约定一下,通配符之间的相邻关系!! (人为约定) --- 约定原因(1,2,3 实现匹配逻辑会很繁琐,功能性上提升不大)
        // 1. aaa.#.#.bbb  ---> 非法 (两个#可以打架了)
        // 2. aaa.#.*.bbb  ---> 非法 (*和#冲突)
        // 3. aaa.*.#.bbb  ---> 非法
        // 4. aaa.*.*.bbb  ---> 合法
        for (int i = 0; i < words.length - 1; i++) {
            // 连续两个 #
            if(words[i].equals("#") && words[i+1].equals("#")) {
                return false;
            }
            // 第一个为#且第二个为*
            if(words[i].equals("#") && words[i+1].equals("*")) {
                return false;
            }
            // 第一个为*且第二个为#
            if(words[i].equals("*") && words[i+1].equals("#")) {
                return false;
            }
        }
        return true;//此处先返回true 后续来制定bindingKey的规则
    }

    //routingKey的构造规则
    // 1, 数字,字母,下划线
    // 2, 使用. 进行分割成若干个部分
    public boolean checkRoutingKey(String routingKey) {
        if(routingKey.length() == 0) {
            // 空字符串认为是合法的情况,比如是 fanout 交换机是使用不上的
            return true;
        }
        for (int i = 0; i < routingKey.length(); i++) {
            char ch = routingKey.charAt(i);
            //判断大写
            if(ch >= 'A' && ch <= 'Z') {
                continue;
            }
            //判定小写
            if (ch >= 'a' && ch <= 'z') {
                continue;
            }
            //判定阿拉伯数字
            if (ch >= '0' && ch <= '9') {
                continue;
            }
            //判定下划线 或者 点
            if (ch == '_' || ch == '.') {
                continue;
            }
            //不符合条件
            return false;
        }
        return true;
    }

    //检查是否可以进行转发
    public boolean route(ExchangeType exchangeType,Binding binding,Message message) throws MqException {
        //根据不同的exchangeType使用不同的转发规则
        if(exchangeType == ExchangeType.FANOUT) {
            //该交换机上绑定的所有的队列都应该转发
            return true;
        } else if (exchangeType == ExchangeType.TOPIC) {
            //主题交换机的转发规则就得细究了
            return routeTopic(binding,message);
        } else {
            //直接交换机在转发代码那块就已经实现了转发逻辑了,按理是跑不到这块的
            throw new MqException("[Router] 交换机类型非法!! exchangeType = " + exchangeType);
        }
    }

    //主题交换机的判断规则 --- 双指针
    private boolean routeTopic(Binding binding, Message message) {
        String[] bindingTokens = binding.getBindingKey().split("\\.");
        String[] routingTokens = message.getRoutingKey().split("\\.");

        //引入两个下标指向上述两个数组,初始都为0
        int bindingIndex = 0;
        int routingIndex = 0;
        //每次循环下标都不一定加1,使用while稳一点
        while(bindingIndex < bindingTokens.length && routingIndex < routingTokens.length) {
            if(bindingTokens[bindingIndex].equals("*")) {
                //[情况二]如果遇到*,双指针移动,直接下一轮
                routingIndex++;
                bindingIndex++;
            } else if(bindingTokens[bindingIndex].equals("#")) {
                //[情况三]如果遇到#,要先看routingKey还有没有下一个部分
                bindingIndex++;
                if (bindingIndex == bindingTokens.length) {
                    // # 后面没需要进行匹配的了
                    return true;
                }
                //[情况四]如果还有下一个位置,则找routingKey后面的部分是有比配的,无则直接寄
                //自定义函数解决查找问题
                routingIndex = findNextMatch(routingTokens,routingIndex,bindingTokens[bindingIndex]);
                if (routingIndex == -1) {
                    return false;
                }
                //双指针移动,直接下一轮
                routingIndex++;
                bindingIndex++;
            } else {
                //[情况一]无特殊情况的了,直接比即可
                if(!bindingTokens[bindingIndex].equals(routingTokens[routingIndex])) {
                    return false;
                }
                routingIndex++;
                bindingIndex++;
            }
        }
        //[情况五]判定是否双方同时到达末尾
        if(routingIndex == routingTokens.length && bindingIndex == bindingTokens.length) {
            return true;
        }
        return false;
    }

    private int findNextMatch(String[] routingTokens, int routingIndex, String bindingToken) {
        for (int i = routingIndex; i < routingTokens.length; i++) {
            if(routingTokens[i].equals(bindingToken)) {
                return i;
            }
        }
        return -1;
    }
}
